package com.oromwin.repos.client;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;

import com.oromwin.repos.common.Logger;
import com.oromwin.repos.common.ReposException;
import com.oromwin.repos.data.FileTable;
import com.oromwin.repos.data.MediaTable;
import com.oromwin.repos.data.VolumeTable;
import com.oromwin.repos.server.request.ListRepositoryRequest;
import com.oromwin.repos.server.request.Request;
import com.oromwin.repos.server.request.file.CopyFileRequest;
import com.oromwin.repos.server.request.file.CreateFileRequest;
import com.oromwin.repos.server.request.file.DeleteFileRequest;
import com.oromwin.repos.server.request.file.InfoFileRequest;
import com.oromwin.repos.server.request.file.MoveFileRequest;
import com.oromwin.repos.server.request.file.ReadFileRequest;
import com.oromwin.repos.server.request.file.RenameFileRequest;
import com.oromwin.repos.server.request.media.CreateMediaRequest;
import com.oromwin.repos.server.request.media.DeleteMediaRequest;
import com.oromwin.repos.server.request.media.InfoMediaRequest;
import com.oromwin.repos.server.request.media.ListMediaRequest;
import com.oromwin.repos.server.request.media.UpdateMediaRequest;
import com.oromwin.repos.server.request.volume.CreateVolumeRequest;
import com.oromwin.repos.server.request.volume.DeleteVolumeRequest;
import com.oromwin.repos.server.request.volume.InfoVolumeRequest;
import com.oromwin.repos.server.request.volume.ListVolumeRequest;
import com.oromwin.repos.server.response.CreateResponse;
import com.oromwin.repos.server.response.ListRepositoryResponse;
import com.oromwin.repos.server.response.NormalResponse;
import com.oromwin.repos.server.response.Response;
import com.oromwin.repos.server.response.file.InfoFileResponse;
import com.oromwin.repos.server.response.file.ReadFileResponse;
import com.oromwin.repos.server.response.media.InfoMediaResponse;
import com.oromwin.repos.server.response.media.ListMediaResponse;
import com.oromwin.repos.server.response.volume.InfoVolumeResponse;
import com.oromwin.repos.server.response.volume.ListVolumeResponse;

public class ReposSocketClient extends AbstractSocketClient {
    private Log log = Logger.getLog(ReposSocketClient.class);
    private String _hostname = null;
    private int _port = 0;

    public ReposSocketClient(String hostname, int port) {
        _hostname = hostname;
        _port = port;
    }

    public List listRepository() throws ReposException {
        List volumeTableList = new ArrayList();
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("listRepository()");
            id = connect(_hostname, _port);
            _req = new ListRepositoryRequest();
            _req.send(getDataOutputStream(id));
            _res = new ListRepositoryResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("listVolume, Result " + _res.getResult());
            if (_res.getResult()) {
                ListRepositoryResponse res = (ListRepositoryResponse) _res;
                volumeTableList = res.getVolumeTableList();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to list repository, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return volumeTableList;
    }

    /**
     * 볼륨을 생성합니다.
     * @param volumeName 볼륨명
     * @return
     */
    public String createVolume(String volumeName) throws ReposException {
        String objectId = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("createVolume('" + volumeName + "')");
            id = connect(_hostname, _port);
            _req = new CreateVolumeRequest(volumeName);
            _req.send(getDataOutputStream(id));
            _res = new CreateResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("createVolume, Result " + _res.getResult());
            if (_res.getResult()) {
                CreateResponse res = (CreateResponse) _res;
                objectId = res.getObjectId();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to create volume, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return objectId;
    }

    /**
     * 볼륨의 삭제플래그를 True (=1)로 변경합니다.
     * @param volumeId
     * @return
     */
    public boolean deleteVolume(String volumeId) throws ReposException {
        return deleteVolume(volumeId, false);
    }

    /**
     * 볼륨을 삭제합니다.
     * @param volumeId 볼륨 아이디
     * @param force 데이터베이스 레코드도 삭제
     * @return
     */
    public boolean deleteVolume(String volumeId, boolean force) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("deleteVolume('" + volumeId + "', '" + force + "')");
            id = connect(_hostname, _port);
            _req = new DeleteVolumeRequest(volumeId, force);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("deleteVolume, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to delete volume, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

    /**
     * 볼륨정보를 가져옵니다.
     * @param volumeId
     * @return
     */
    public VolumeTable infoVolume(String volumeId) throws ReposException {
        VolumeTable volumeTable = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("infoVolume('" + volumeId + "')");
            id = connect(_hostname, _port);
            _req = new InfoVolumeRequest(volumeId);
            _req.send(getDataOutputStream(id));
            _res = new InfoVolumeResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("infoVolume, Result " + _res.getResult());
            if (_res.getResult()) {
                InfoVolumeResponse res = (InfoVolumeResponse) _res;
                volumeTable = res.getVolumeTable();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to info volume, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return volumeTable;
    }

    /**
     * 볼륨에 포함된 미디어 목록을 가져옵니다.
     * @param volumeId
     * @return
     */
    public List listVolume(String volumeId) throws ReposException {
        List mediaTableList = new ArrayList();
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("listVolume('" + volumeId + "')");
            id = connect(_hostname, _port);
            _req = new ListVolumeRequest(volumeId);
            _req.send(getDataOutputStream(id));
            _res = new ListVolumeResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("listVolume, Result " + _res.getResult());
            if (_res.getResult()) {
                ListVolumeResponse res = (ListVolumeResponse) _res;
                mediaTableList = res.getMediaTableList();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to list volume, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return mediaTableList;
    }

    /**
     * 미디어를 생성합니다.
     * @param volumeId 볼륨명
     * @param mediaPath 미디어 저장을 위한 서버의 물리적인 경로
     * @param mediaMaxBytes 해당 미디어의 최대 디스크 용량
     * @return
     */
    public String createMedia(String volumeId, File mediaPath, long mediaMaxBytes) throws ReposException {
        String objectId = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("createMedia('" + volumeId + "', '" + mediaPath + "', '" + mediaMaxBytes + "')");
            id = connect(_hostname, _port);
            _req = new CreateMediaRequest(volumeId, mediaPath, mediaMaxBytes);
            _req.send(getDataOutputStream(id));
            _res = new CreateResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("createMedia, Result " + _res.getResult());
            if (_res.getResult()) {
                CreateResponse res = (CreateResponse) _res;
                objectId = res.getObjectId();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to create media, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return objectId;
    }

    /**
     * 미디어의 최대 용량을 변경합니다.
     * @param mediaId
     * @param mediaMaxBytes
     * @return
     */
    public boolean updateMedia(String mediaId, long mediaMaxBytes) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("updateMedia('" + mediaId + "', '" + mediaMaxBytes + "')");
            id = connect(_hostname, _port);
            _req = new UpdateMediaRequest(mediaId, mediaMaxBytes);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("updateMedia, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to update media, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

    /**
     * 미디어의 삭제플래그를 True (=1)로 변경합니다.
     * @param mediaId
     * @return
     */
    public boolean deleteMedia(String mediaId) throws ReposException {
        return deleteMedia(mediaId, false);
    }

    /**
     * 미디어를 삭제합니다.
     * @param mediaId 미디어 저장을 위한 서버의 물리적인 경로
     * @param force 물리적인 파일과 데이터베이스 레코드도 삭제
     * @return
     */
    public boolean deleteMedia(String mediaId, boolean force) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("deleteMedia('" + mediaId + "', '" + force + "')");
            id = connect(_hostname, _port);
            _req = new DeleteMediaRequest(mediaId, force);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("deleteMedia, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to delete media, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

    /**
     * 미디어정보를 가져옵니다.
     * @param mediaId
     * @return
     */
    public MediaTable infoMedia(String mediaId) throws ReposException {
        MediaTable mediaTable = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("infoMedia('" + mediaId + "')");
            id = connect(_hostname, _port);
            _req = new InfoMediaRequest(mediaId);
            _req.send(getDataOutputStream(id));
            _res = new InfoMediaResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("infoMedia, Result " + _res.getResult());
            if (_res.getResult()) {
                InfoMediaResponse res = (InfoMediaResponse) _res;
                mediaTable = res.getMediaTable();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to info media, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return mediaTable;
    }

    /**
     * 미디어에 포함된 파일 목록을 가져옵니다.
     * @param mediaId
     * @return
     */
    public List listMedia(String mediaId) throws ReposException {
        List fileTableList = new ArrayList();
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("listMedia('" + mediaId + "')");
            id = connect(_hostname, _port);
            _req = new ListMediaRequest(mediaId);
            _req.send(getDataOutputStream(id));
            _res = new ListMediaResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("listMedia, Result " + _res.getResult());
            if (_res.getResult()) {
                ListMediaResponse res = (ListMediaResponse) _res;
                fileTableList = res.getFileTableList();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to list media, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return fileTableList;
    }

    /**
     * 파일을 생성합니다.
     * @param volumeId 볼륨 아이디
     * @param filePath
     * @return
     */
    public String createFile(String volumeId, File filePath) throws ReposException {
        String objectId = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("createFile('" + volumeId + "', '" + filePath + "')");
            id = connect(_hostname, _port);
            _req = new CreateFileRequest(volumeId, filePath);
            _req.send(getDataOutputStream(id));
            _res = new CreateResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("createFile, Result " + _res.getResult());
            if (_res.getResult()) {
                CreateResponse res = (CreateResponse) _res;
                objectId = res.getObjectId();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to create file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return objectId;
    }

    /**
     * 파일을 로컬로 다운로드 하여 저장합니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @param localPath 저장될 로컬 폴더 및 파일명
     * @return
     */
    public boolean readFile(String fileId, File localPath) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("readFile('" + fileId + "', '" + localPath + "')");
            id = connect(_hostname, _port);
            _req = new ReadFileRequest(fileId);
            _req.send(getDataOutputStream(id));
            _res = new ReadFileResponse(localPath);
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("readFile, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (!localPath.exists()) {
                throw ReposException.create(log, "Failed to create file {" + localPath + "}");
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to read file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } catch (NullPointerException e) {
            String message = "Destination path is null, path " + localPath;
            throw ReposException.create(log, e, message);
        } catch (SecurityException e) {
            String message = "Can't access to requested directory, path " + localPath;
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

    /**
     * 파일의 삭제플래그를 True (=1)로 변경합니다.
     * @param fileId
     * @return
     */
    public boolean deleteFile(String fileId) throws ReposException {
        return deleteFile(fileId, false);
    }

    /**
     * 파일을 삭제합니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @param force 물리적인 파일과 데이터베이스 레코드도 삭제
     * @return
     */
    public boolean deleteFile(String fileId, boolean force) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("deleteFile('" + fileId + "', '" + force + "')");
            id = connect(_hostname, _port);
            _req = new DeleteFileRequest(fileId, force);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("deleteFile, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to delete file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

    /**
     * 파일을 지정한 미디어로 복사합니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @param volumeId 파일이 복사될 볼륨 아이디
     * @return
     * @see fileId가 위치하는 볼륨 내의 미디어 경로 중의 하나로만 복사 또는 이동이 가능
     */
    public String copyFile(String fileId, String volumeId) throws ReposException {
        String objectId = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("copyFile('" + fileId + "', '" + volumeId + "')");
            id = connect(_hostname, _port);
            _req = new CopyFileRequest(fileId, volumeId);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("copyFile, Result " + _res.getResult());
            if (_res.getResult()) {
                CreateResponse res = (CreateResponse) _res;
                objectId = res.getObjectId();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to copy file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return objectId;
    }

    /**
     * 파일을 지정한 미디어로 이동합니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @param volumeId 파일이 이동될 미디어 경로 아이디
     * @return
     * @see fileId가 위치하는 볼륨 내의 미디어 경로 중의 하나로만 복사 또는 이동이 가능
     */
    public String moveFile(String fileId, String volumeId) throws ReposException {
        String objectId = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("moveFile('" + fileId + "', '" + volumeId + "')");
            id = connect(_hostname, _port);
            _req = new MoveFileRequest(fileId, volumeId);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("moveFile, Result " + _res.getResult());
            if (_res.getResult()) {
                CreateResponse res = (CreateResponse) _res;
                objectId = res.getObjectId();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to copy file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return objectId;
    }

    /**
     * 파일의 정보를 가져옵니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @return
     */
    public FileTable infoFile(String fileId) throws ReposException {
        FileTable fileTable = null;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("infoFile('" + fileId + "')");
            id = connect(_hostname, _port);
            _req = new InfoFileRequest(fileId);
            _req.send(getDataOutputStream(id));
            _res = new InfoFileResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("infoFile, Result " + _res.getResult());
            if (_res.getResult()) {
                InfoFileResponse res = (InfoFileResponse) _res;
                fileTable = res.getFileTable();
            } else {
                throw ReposException.create(log, _res.getDescription());
            }
        } catch (ReposException e) {
            String message = "Failed to info file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return fileTable;
    }

    /**
     * 파일명을 변경합니다.
     * @param fileId 파일 저장시에 생성된 파일 아이디
     * @param filename 변경할 파일명
     * @return
     */
    public boolean renameFile(String fileId, String filename) throws ReposException {
        boolean result = false;
        Request _req = null;
        Response _res = null;
        String id = null;
        try {
            if (log.isDebugEnabled()) log.debug("renameFile('" + fileId + "', '" + filename + "')");
            id = connect(_hostname, _port);
            _req = new RenameFileRequest(fileId, filename);
            _req.send(getDataOutputStream(id));
            _res = new NormalResponse();
            _res.receive(getDataInputStream(id));
            if (log.isDebugEnabled()) log.debug("renameFile, Result " + _res.getResult());
            result = _res.getResult();
            if (!result) {
                throw ReposException.create(log, _res.getDescription());
            }
            if (log.isDebugEnabled()) log.debug(_res.getDescription());
        } catch (ReposException e) {
            String message = "Failed to rename file, cause " + e.getMessage();
            throw ReposException.create(log, e, message);
        } finally {
            try { disconnect(id); } catch (ReposException e) { }
        }
        return result;
    }

}
